Hacks动画篇-Hack4 在Canvas上显示动画

在Canvas上显示动画

作者:李旺成
时间:2016年5月11日


这个 Hack 将介绍如何使用 Canvas 类在屏幕上绘制图形,并为其添加动画效果。

理解 Canvas

Canvas 类就是表示一块画布,你可以在上面画你想画的东西。

Canvas 是 Android 2D 绘图中的一个关键类,先看一下官方对 Canvas 的简介:

Canvas类

好吧!确实很简洁,看了之后基本上不知道何为 Canvas(反正我是这个感觉),里面就一句话有用:”For more information about how to use Canvas, read the Canvas and Drawables developer guide.“(就是查看详情)

英文阅读无障碍的同学请自行点击上面的链接跳转过去阅读,不喜欢看英文的,这里提供一段《50 Android Hacks》上对 Canvas 的介绍(从以前文档翻译来的):
”可以把 Canvas 视为 Surface 的替身或者接口,图形便是绘制在 Surface 上的。Canvas 封装了所有的绘图调用。通过 Canvas,绘制到 Surface 上的内容首先存储到与之关联的 Bitmap 中,该 Bitmap 最终会呈现到窗口上。“

简而言之,Canvas 就是画布,可以在上面画各种图形或图像。下面来看看如何使用 Canvas。

Canvas 简单使用

这里以画一个红色方块为例介绍一下 Canvas 的使用。效果如下:
Canvas 简单使用

获取 Canvas

获取 Canvas 一般有两种方式:
1、从 View 的 onDraw() 方法中获取
看一下 View onDraw() 方法的方法签名:

1
protected void onDraw(Canvas canvas);

自定义 View 一般会重写 onDraw() 方法,这时候 View 中的 Canvas 对象会被当做参数传递过来,可以直接操作这个 Canvas (Google 也建议使用 onDraw() 传入的 Canvas),对该 Canvas 的效果会直接反应在 View 中。

2、自行创建 Canvas 对象
Canvas 类提供了两个 public 的构造方法。可以直接调用构造方法创建对象:

1
2
3
4
5
6
7
Bitmap b = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
// 创建 Canvas 方式一
Canvas c1 = new Canvas();
c1.setBitmap(b);

// 创建 Canvas 方式二
Canvas c2 = new Canvas(b);

Canvas 需要以 Bitmap 为操作对象,无论哪种方式创建的 Canvas 都是需要传入自定义的 Bitmap。

当使用自己创建的 Canvas 在 bitmap 上执行完绘制操作后,可以将绘制的结果转交给另外一个 Canvas,这样就可以达到两个 Canvas 协作完成的效果,简化逻辑。(参考自:Android Canvas绘图详解(图文)

使用 Canvas 绘制方块

直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建画笔
Paint paint = new Paint();
// 设置画笔颜色
paint.setARGB(255, 255, 0, 0);
// 设置抗锯齿
paint.setAntiAlias(true);
// 创建矩形
RectF rect = new RectF(50, 50, 50, 50);
// 绘制矩形
canvas.drawRoundRect(rect,
100, //x轴的半径
100, //y轴的半径
paint);

好了,准备工作完成了,下面来看看如何在 Canvas 上显示动画。

在 Canvas 上显示动画

我们想实现这样一个效果:一个红色的方块在屏幕上弹跳,当触碰到屏幕边缘的时候就会弹开。具体效果如下动图所示:

在 Canvas 上显示动画

思路分析

这里的关键是如何让”红色方块“动起来,一般可能可以想到这么个思路:启动一个线程,每隔一段时间改变方块的位置,然后刷新视图显示。

是的,用这种方式可以实现,但是有没有想过是否有更简单的方式?如果有很多需要持续运行的元素,这样做是不是会很麻烦,而且会影响性能(开启子线程挺消耗性能的)。

不知道是否听过这样一种最佳实践的提示:
不要在 onDraw() 方法中创建对象,或者使用临时对象,应该该方法会频繁调用。

好了,我的思路是:
利用 View 调用 invalidate() 方法请求重新绘制视图时会调用该视图的 onDraw() 方法,以到达循环调用的目的。在每次 onDraw() 的时候都改变”红色方块“的坐标,这样就可以实现不停的运动了。

具体实现

上面介绍了实现的思路,我们这里的具体实现和上面说的稍有不同,但是,实际上是一样的。

创建红色方块视图

这个红色方块需要提供绘制”红色方块“的实现,以及坐标改变的方法,实现代码如下(Rectangle.java):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Rectangle extends View {

...

public void move() {
moveTo(mSpeedX, mSpeedY);
}

// 真正的移动方法
private void moveTo(int goX, int goY) {
// check the borders, and set the direction if a border has reached
if (mCoordX > (mDrawView.width - MAX_SIZE)) {
goRight = false;
}

if (mCoordX < 0) {
goRight = true;
}

if (mCoordY > (mDrawView.height - MAX_SIZE)) {
goDown = false;
}
if (mCoordY < 0) {
goDown = true;
}

// move the x and y
if (goRight) {
mCoordX += goX;
} else {
mCoordX -= goX;
}
if (goDown) {
mCoordY += goY;
} else {
mCoordY -= goY;
}

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

mDrawRect.set(mCoordX, mCoordY, mCoordX + mRealSize, mCoordY
+ mRealSize);
canvas.drawRoundRect(mDrawRect, 0, 0, mInnerPaint);
}

...

}

这里截取了关键代码,具体代码请自行下载项目代码。

绘制方块

专门提供一个类 DrawView.java 用于负责在屏幕上绘制上面创建的”方块视图“。看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DrawView extends View {
private Rectangle mRectangle;
public int width;
public int height;

public DrawView(Context context) {
super(context);

mRectangle = new Rectangle(context, this);
mRectangle.setARGB(255, 255, 0, 0);
mRectangle.setSpeedX(3);
mRectangle.setSpeedY(3);
}

@Override
protected void onDraw(Canvas canvas) {
invalidate();

mRectangle.move();
mRectangle.onDraw(canvas);
}

}

Rectangle 将在 DrawView 的 width 和 height 范围内运动,真正导致运动的原因是 onDraw() 方法中调用了 invalidate() 方法,该方法会请求重绘,这时 Rectangle 的坐标变化了,所以就出现了移动的效果。

显示 DrawView

你把它当作普通的自定义 View 使用即可,这里将 DrawView 做为 Activity 的内容视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CanvasAnimActivity extends AppCompatActivity {

private DrawView mDrawView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 获取屏幕尺寸
Display display = getWindowManager().getDefaultDisplay();
mDrawView = new DrawView(this);
// 这里简单简单起见为状态栏和ActionBar的高度取了个固定值 200
mDrawView.height = display.getHeight() - 200;
mDrawView.width = display.getWidth();

setContentView(mDrawView);
}
}

运行起来就可以看到红色方块运动的效果了。

小结

这个 Hack 的代码其实挺简单的,关键是理解”方块“是如何动起来的。

在 onDraw() 方法中通过调用 invalidate() 方法变换视图的位置实现自定义动画的简单方法。当然,使用这个小技巧来处理游戏的主循环也是一个不错的方案。

项目地址

AndroidHacks合集
动画篇

项目示例代码:
CanvasAnimActivity.java
Rectangle.java
DrawView.java

参考

Android Canvas绘图详解(图文)
Android——Canvas类的使用
Canvas and Drawables

坚持原创技术分享,您的支持将鼓励我继续创作!